package solver

import (
	"cmp"
	"log"
	"slices"
)

func equalCells(x, y *Cell) bool {
	return x.rowNum == y.rowNum && x.colNum == y.colNum
}

func containCell(xs []*Cell, y *Cell) bool {
	for _, x := range xs {
		if equalCells(x, y) {
			return true
		}
	}
	return false
}

func compareCells(x, y *Cell) int {
	return cmp.Compare(x.rowNum*9+x.colNum, y.rowNum*9+y.colNum)
}

func shareCells(as, bs []*Cell) []*Cell {
	s := []*Cell{}
	slices.SortFunc(as, compareCells)
	slices.SortFunc(bs, compareCells)
	as = slices.CompactFunc(as, equalCells)
	bs = slices.CompactFunc(bs, equalCells)
	i, j := 0, 0
	for i < len(as) && j < len(bs) {
		switch compareCells(as[i], bs[j]) {
		case 0:
			s = append(s, as[i])
			i++
			j++
		case 1:
			j++
		case -1:
			i++
		}
	}
	return s
}

func intsFromBools(xs [9]bool) []int {
	ys := []int{}
	for i, x := range xs {
		if x {
			ys = append(ys, i)
		}
	}
	return ys
}

func (g *Grid) clearupWing(xyORzCell, xzCell *Cell, yzCell *Cell) (used bool) {
	xzRelCells := g.seenCells(xzCell)
	yzRelCells := g.seenCells(yzCell)
	relCells := shareCells(xzRelCells, yzRelCells)
	strategy := "XYWing"

	if xyORzCell.candCount == 3 {
		xyzRelCells := g.seenCells(xyORzCell)
		relCells = shareCells(relCells, xyzRelCells)
		strategy = "XYZWing"
	}

	z := intersect(intsFromBools(xzCell.cands), intsFromBools(yzCell.cands))[0]
	for _, cell := range relCells {
		if equalCells(cell, xyORzCell) || equalCells(cell, xzCell) || equalCells(cell, yzCell) {
			continue
		}
		if cell.removeCand(z) {
			used = true
			log.Printf("(r%vc%v, r%vc%v, r%vc%v) %v %v", xyORzCell.rowNum, xyORzCell.colNum, xzCell.rowNum, xzCell.colNum, yzCell.rowNum, yzCell.colNum, z+1, strategy)
		}
	}
	return
}

func (g *Grid) seenCells(cell *Cell) []*Cell {
	cells := []*Cell{}
	cells = append(cells, g.boxes[cell.boxNum].cells[:]...)
	cells = append(cells, g.rows[cell.rowNum].cells[:]...)
	cells = append(cells, g.cols[cell.colNum].cells[:]...)
	return cells
}

/*
func isXYWingCands(pivot, wing1, wing2 *Cell) bool {
	if wing1.cands == wing2.cands {
		return false
	}
	xy := symDifference(intsFromBools(wing1.cands), intsFromBools(wing2.cands))
	slices.Sort(xy)
	return slices.Equal(xy, intsFromBools(pivot.cands))
}

func isXYZWingCands(pivot, wing1, wing2 *Cell) bool {
	xyz := union(intsFromBools(wing1.cands), intsFromBools(wing2.cands))
	slices.Sort(xyz)
	return slices.Equal(xyz, intsFromBools(pivot.cands))
}

func isWingCands(pivot, wing1, wing2 *Cell) bool {
	candCount := pivot.candCount
	if candCount == 2 {
		return isXYWingCands(pivot, wing1, wing2)
	} else { //==3
		return isXYZWingCands(pivot, wing1, wing2)
	}
}

func (g *Grid) wingHandle(pivot, wing1, wing2 *Cell) (used bool) {
	if wing1.candCount != 2 {
		return false
	}
	if wing2.candCount != 2 || wing2.boxNum == pivot.boxNum {
		return false
	}
	if !isWingCands(pivot, wing1, wing2) {
		return false
	}
	if g.clearupWing(pivot, wing1, wing2) {
		used = true
	}
	return
}
*/
/*
	func (g *Grid) wingCell(pivot *Cell) (used bool) {
		for _, wing1 := range g.boxes[pivot.boxNum].cells {
			for _, wing2 := range g.rows[pivot.rowNum].cells {
				if g.wingHandle(pivot, wing1, wing2) {
					used = true
				}
			}
			for _, wing2 := range g.cols[pivot.colNum].cells {
				if g.wingHandle(pivot, wing1, wing2) {
					used = true
				}
			}
		}
		for _, wing1 := range g.rows[pivot.rowNum].cells {
			for _, wing2 := range g.cols[pivot.colNum].cells {
				if g.wingHandle(pivot, wing1, wing2) {
					used = true
				}
			}
		}
		return
	}

//
*/
func (g *Grid) XYWing() (used bool) {
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.candCount != 2 {
				continue
			}
			if g.wingCell(cell) {
				used = true
			}
		}
	}
	return
}

func (g *Grid) XYZWing() (used bool) {
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.candCount != 3 {
				continue
			}
			if g.wingCell(cell) {
				used = true
			}
		}
	}
	return
}

type StrongLink struct {
	cand  int
	cells [2]*Cell
}

func findStrongLinks(house House) (links []StrongLink) {
	candCells, candCounts := sumupCandCells(house)
	for cand, candCount := range candCounts {
		if candCount != 2 {
			continue
		}
		link := StrongLink{}
		link.cand = cand
		nums := intsFromBools(candCells[cand])
		link.cells[0] = house.cells[nums[0]]
		link.cells[1] = house.cells[nums[1]]
		links = append(links, link)
	}
	return links
}

func (g *Grid) WWingFromLink(link StrongLink) (used bool) {
	var cand int
	for _, wing1 := range g.seenCells(link.cells[0]) {
		if equalCells(wing1, link.cells[0]) || equalCells(wing1, link.cells[1]) || wing1.candCount != 2 {
			continue
		}
		wing1Cands := intsFromBools(wing1.cands)
		if wing1Cands[0] == link.cand {
			cand = wing1Cands[1]
		} else if wing1Cands[1] == link.cand {
			cand = wing1Cands[0]
		} else {
			continue
		}
		for _, wing2 := range g.seenCells(link.cells[1]) {
			if equalCells(wing2, link.cells[0]) || equalCells(wing2, link.cells[1]) || wing2.candCount != 2 {
				continue
			}
			wing2Cands := intsFromBools(wing2.cands)
			want := []int{link.cand, cand}
			slices.Sort(want)
			if !slices.Equal(wing2Cands, want) {
				continue
			}
			if g.clearupSeenFromCells(cand, []*Cell{wing1, wing2}) {
				used = true
			}

		}

	}
	return
}

func (g *Grid) WWingHouse(house House) (used bool) {
	strongLinks := findStrongLinks(house)
	for _, strongLink := range strongLinks {
		if g.WWingFromLink(strongLink) {
			used = true
		}
	}
	return
}

func (g *Grid) WWing() (used bool) {
	return g.doStrategy(g.WWingHouse)
}

func (g *Grid) verifyTwoWings(pivot *Cell, wings []*Cell) bool {
	if pivot.candCount == 2 {
		return candXor(wings[0].cands, wings[1].cands) == pivot.cands
	} else { //== 3
		return candOr(wings[0].cands, wings[1].cands) == pivot.cands
	}
	//return true
}

func (g *Grid) wingCell(pivot *Cell) (used bool) {
	for _, wings := range g.wingslist(pivot, 2) {
		if !g.verifyTwoWings(pivot, wings) {
			continue
		}
		if g.clearupWing(pivot, wings[0], wings[1]) {
			used = true
		}
	}

	return
}
